package ar.edu.sccs.model.loader;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import ar.edu.sccs.dao.ProjectDTO;
import ar.edu.sccs.dao.RepositoryDTO;
import ar.edu.sccs.entities.Case;
import ar.edu.sccs.entities.Dimension;
import ar.edu.sccs.model.loader.dto.DataClazz;
import ar.edu.sccs.model.loader.dto.DataClazzField;
import ar.edu.sccs.model.loader.dto.DataMethod;
import ar.edu.sccs.model.loader.dto.DataMethodParameter;
import ar.edu.sccs.model.loader.dto.DataProject;
import ar.edu.sccs.model.loader.dto.DataSCM;
import ar.edu.sccs.model.loader.dto.Data.Visibility;
import ar.edu.sccs.monitoring.logging.SCCSLogger;
import ar.edu.sccs.util.Constants;

public class ReflectionLoader {

	static Logger logger = SCCSLogger.getLogger(ReflectionLoader.class);

	@Deprecated
	public static DataClazz getClassInfo(File clazzFile, File clazzDirectory) {
		Class clazz;
		ClassLoader clazzLoader = java.lang.ClassLoader.getSystemClassLoader();

		try {
			// Obtengo Path absolutos
			String clazzFileAbsolutePath = clazzFile.getAbsolutePath();
			String clazzDirectoryAbsolutePath = clazzDirectory.getAbsolutePath();

			// Obtengo el Path absoluto desde el directorion BIN como raiz
			String clazzCanonicalName = clazzFileAbsolutePath.substring(clazzDirectoryAbsolutePath.length() + 1, clazzFileAbsolutePath
					.lastIndexOf("."));
			// Reemplazo los separadores de directorios por '.'
			clazzCanonicalName = clazzCanonicalName.replace(System.getProperty("file.separator"), ".");

			clazz = clazzLoader.loadClass(clazzCanonicalName);
			DataClazz dataClazz = getClassInfo(clazz, clazzFileAbsolutePath);
			return dataClazz;

		} catch (Exception e) {
			logger.error("ReflectionLoader.getClassInfo ERROR", e);
			return null;
		}
	}

	public static DataClazz getClassInfo(DataProject clazzProject, DataSCM clazzRepository, String clazzFileAbsolutePath, List<String> clazzDirectoryAbsolutePath) {
		Class clazz;
		ClassLoader clazzLoader = java.lang.ClassLoader.getSystemClassLoader();

		try {
			String clazzCanonicalName = new String();
			for (String string : clazzDirectoryAbsolutePath) {
				if (clazzFileAbsolutePath.contains(string)) {
					// // Obtengo el Path absoluto desde el directorion BIN como
					// raiz
					clazzCanonicalName = clazzFileAbsolutePath.substring(string.length() + 1, clazzFileAbsolutePath.lastIndexOf("."));
					// Reemplazo los separadores de directorios por '.'
					clazzCanonicalName = clazzCanonicalName.replace(System.getProperty("file.separator"), ".");
					clazzCanonicalName = clazzCanonicalName.replace(Constants.Loader.FILE_SEPARATOR, ".");

					DataClazz dataClazz;
					try {
						clazz = clazzLoader.loadClass(clazzCanonicalName);
						dataClazz = getClassInfo(clazz, clazzFileAbsolutePath);
						dataClazz.setClazzProject(clazzProject);
						dataClazz.setClazzRepository(clazzRepository);
						return dataClazz;
					} catch (Exception e) {
						logger.error("ReflectionLoader.getClassInfo loadClass ERROR", e);
						break;
					}
				}
			}

			return null;
		} catch (Exception e) {
			logger.error("ReflectionLoader.getClassInfo ERROR", e);
			return null;
		}
	}

	private static DataClazz getClassInfo(Class clazz, String sourcePath) {
		DataClazz dataClazz = new DataClazz();

		// SOURCE.PATH
		dataClazz.setSourcePath(sourcePath);

		// CLASS.PACKAGE
		dataClazz.setInterface(clazz.isInterface());
		dataClazz.setEnum(clazz.isEnum());

		// CLASS.PACKAGE
		dataClazz.setClazzPackage(clazz.getPackage().getName());
		// CLASS.NAME
		dataClazz.setClazzName(clazz.getSimpleName());
		// CLASS.EXTEND
		dataClazz.setClazzExtend(clazz.getSuperclass());

		// CLASS.FIELDS
		for (Field clazzField : clazz.getDeclaredFields()) {
			dataClazz.addField(new DataClazzField(clazzField.getName(), clazzField.getType()));
		}

		// CLASS.IMPLEMENTS
		for (Class claszImplement : clazz.getInterfaces()) {
			dataClazz.addClazzImplement(claszImplement);
		}

		int clazzModifier = clazz.getModifiers();
		dataClazz.setModifiers(clazzModifier);

		// CLASS.MODIFIERS
		dataClazz.setAbstract((clazzModifier & Modifier.ABSTRACT) == Modifier.ABSTRACT);
		dataClazz.setFinal((clazzModifier & Modifier.FINAL) == Modifier.FINAL);
		dataClazz.setInterface((clazzModifier & Modifier.INTERFACE) == Modifier.INTERFACE);
		dataClazz.setStrictfp((clazzModifier & Modifier.STRICT) == Modifier.STRICT);

		// CLASS.VISIBILITY
		if ((clazzModifier & Modifier.PUBLIC) == Modifier.PUBLIC)
			dataClazz.setVisibility(Visibility.PUBLIC_VISIBILITY);
		else if ((clazzModifier & Modifier.PRIVATE) == Modifier.PRIVATE)
			dataClazz.setVisibility(Visibility.PRIVATE_VISIBILITY);
		else if ((clazzModifier & Modifier.PROTECTED) == Modifier.PROTECTED)
			dataClazz.setVisibility(Visibility.PROTECTED_VISIBILITY);
		else
			dataClazz.setVisibility(Visibility.NOTHING_VISIBILITY);

		// METHOD
		for (Method method : clazz.getDeclaredMethods()) {
			DataMethod clazzMethod = new DataMethod();

			// METHOD.NAME
			clazzMethod.setMethodName(method.getName());

			// METHOD.RETURN
			clazzMethod.setMethodReturn(method.getReturnType());

			// METHOD.PARAMETER
			for (Class methodParameter : method.getParameterTypes()) {
				clazzMethod.addMethodParameter(new DataMethodParameter(methodParameter));
			}

			// METHOD.EXCEPTION
			for (Class methodException : method.getExceptionTypes()) {
				clazzMethod.addMethodException(methodException);
			}

			int methodModifier = method.getModifiers();
			clazzMethod.setModifiers(methodModifier);

			// CLASS.MODIFIERS
			clazzMethod.setAbstract((methodModifier & Modifier.ABSTRACT) == Modifier.ABSTRACT);
			clazzMethod.setFinal((methodModifier & Modifier.FINAL) == Modifier.FINAL);
			clazzMethod.setNative((methodModifier & Modifier.NATIVE) == Modifier.NATIVE);
			clazzMethod.setStatic((methodModifier & Modifier.STATIC) == Modifier.STATIC);
			clazzMethod.setSynchronized((methodModifier & Modifier.SYNCHRONIZED) == Modifier.SYNCHRONIZED);
			clazzMethod.setStrictfp((methodModifier & Modifier.STRICT) == Modifier.STRICT);

			// METHOD.VISIBILITY
			if ((methodModifier & Modifier.PUBLIC) == Modifier.PUBLIC)
				clazzMethod.setVisibility(Visibility.PUBLIC_VISIBILITY);
			else if ((methodModifier & Modifier.PRIVATE) == Modifier.PRIVATE)
				clazzMethod.setVisibility(Visibility.PRIVATE_VISIBILITY);
			else if ((methodModifier & Modifier.PROTECTED) == Modifier.PROTECTED)
				clazzMethod.setVisibility(Visibility.PROTECTED_VISIBILITY);
			else
				clazzMethod.setVisibility(Visibility.NOTHING_VISIBILITY);

			dataClazz.addMethod(clazzMethod);
		}

		return dataClazz;
	}

	public static List<Case> loadCaseInfo(List<DataClazz> dataClazz) {
		List<Case> caseList = new ArrayList<Case>();

		for (DataClazz clazz : dataClazz) {
			for (DataMethod method : clazz.getMethods()) {

				Case newCase = new Case();
				newCase.setScmUrl(clazz.getSourcePath());
				newCase.setCalificationId(Constants.Dimension.Calification.SEEN);

				Dimension clazzType = new Dimension(clazz.isInterface() ? "interface" : clazz.isEnum() ? "emun" :"class");
				newCase.putDimension(Constants.Dimension.Class.TYPE, clazzType);

				Dimension clazzName = new Dimension(clazz.getClazzName());
				newCase.putDimension(Constants.Dimension.Class.NAME, clazzName);

				Dimension clazzPackage = new Dimension(clazz.getClazzPackage());
				newCase.putDimension(Constants.Dimension.Class.PACKAGE, clazzPackage);

				Dimension clazzExtend = new Dimension(clazz.getClazzExtend());
				if (clazz.getClazzExtend() != null)
					clazzExtend = new Dimension(StringUtils.substringAfterLast(clazz.getClazzExtend().getCanonicalName(), "."));
				newCase.putDimension(Constants.Dimension.Class.EXTEND, clazzExtend);

				String clazzImplementsData = null;
				if (clazz.getClazzImplements().size() > 0) {
					clazzImplementsData = new String();
					for (Class implmentedClazz : clazz.getClazzImplements())
						clazzImplementsData += (StringUtils.substringAfterLast(implmentedClazz.getCanonicalName(), ".") + "|");
				}
				Dimension clazzImplements = new Dimension(StringUtils.chomp(clazzImplementsData, "|"));
				newCase.putDimension(Constants.Dimension.Class.IMPLEMENTS, clazzImplements);

				Dimension clazzIsPublic = new Dimension(clazz.getVisibility() == Visibility.PUBLIC_VISIBILITY);
				newCase.putDimension(Constants.Dimension.Class.PUBLIC, clazzIsPublic);

				Dimension clazzIsProtected = new Dimension(clazz.getVisibility() == Visibility.PROTECTED_VISIBILITY);
				newCase.putDimension(Constants.Dimension.Class.PROTECTED, clazzIsProtected);

				Dimension clazzIsPrivate = new Dimension(clazz.getVisibility() == Visibility.PRIVATE_VISIBILITY);
				newCase.putDimension(Constants.Dimension.Class.PRIVATE, clazzIsPrivate);

				Dimension clazzIsAbstract = new Dimension(clazz.isAbstract());
				newCase.putDimension(Constants.Dimension.Class.ABSTRACT, clazzIsAbstract);

				Dimension clazzIsFinal = new Dimension(clazz.isFinal());
				newCase.putDimension(Constants.Dimension.Class.FINAL, clazzIsFinal);

				Dimension clazzIsStrictfp = new Dimension(clazz.isStrictfp());
				newCase.putDimension(Constants.Dimension.Class.STRICTFP, clazzIsStrictfp);

				Dimension methodName = new Dimension(method.getMethodName());
				newCase.putDimension(Constants.Dimension.Method.NAME, methodName);

				Dimension methodReturn = new Dimension(method.getMethodReturn());
				if (! method.getMethodReturn().isPrimitive())
					if (method.getMethodReturn().getCanonicalName().contains(".")) {
						methodReturn = new Dimension(StringUtils.substringAfterLast(method.getMethodReturn().getCanonicalName(), "."));
					} else {
						methodReturn = new Dimension(method.getMethodReturn().getCanonicalName());
					}
				newCase.putDimension(Constants.Dimension.Method.RETURN, methodReturn);

				String methodParamsData = null;
				if (method.getMethodParameters().size() > 0) {
					methodParamsData = new String();
					for (DataMethodParameter methodParameter : method.getMethodParameters()) {
						if (methodParameter.getParameterType().isPrimitive()) {
							methodParamsData += methodParameter.getParameterType() + "|";
						} else {
							methodParamsData += (StringUtils.substringAfterLast(methodParameter.getParameterType().getCanonicalName(), ".") + "|");
						}
					}
				}
				Dimension methodParams = new Dimension(StringUtils.chomp(methodParamsData, "|"));
				newCase.putDimension(Constants.Dimension.Method.PARAMS, methodParams);

				String methodExceptionsData = null;
				if (method.getMethodExceptions().size() > 0) {
					methodExceptionsData = new String();
					for (Class exceptionClazz : method.getMethodExceptions())
						methodExceptionsData += (StringUtils.substringAfterLast(exceptionClazz.getCanonicalName(), ".") + "|");
				}
				Dimension methodExceptions = new Dimension(StringUtils.chomp(methodExceptionsData, "|"));
				newCase.putDimension(Constants.Dimension.Method.EXCEPTIONS, methodExceptions);

				Dimension methodIsPublic = new Dimension(method.getVisibility() == Visibility.PUBLIC_VISIBILITY);
				newCase.putDimension(Constants.Dimension.Method.PUBLIC, methodIsPublic);

				Dimension methodIsProtected = new Dimension(method.getVisibility() == Visibility.PROTECTED_VISIBILITY);
				newCase.putDimension(Constants.Dimension.Method.PROTECTED, methodIsProtected);

				Dimension methodIsPrivate = new Dimension(method.getVisibility() == Visibility.PRIVATE_VISIBILITY);
				newCase.putDimension(Constants.Dimension.Method.PRIVATE, methodIsPrivate);

				Dimension methodIsAbstract = new Dimension(method.isAbstract());
				newCase.putDimension(Constants.Dimension.Method.ABSTRACT, methodIsAbstract);

				Dimension methodIsFinal = new Dimension(method.isFinal());
				newCase.putDimension(Constants.Dimension.Method.FINAL, methodIsFinal);

				Dimension methodIsStrictfp = new Dimension(method.isStrictfp());
				newCase.putDimension(Constants.Dimension.Method.STRICTFP, methodIsStrictfp);

				Dimension methodIsNative = new Dimension(method.isNative());
				newCase.putDimension(Constants.Dimension.Method.NATIVE, methodIsNative);

				Dimension methodIsStatic = new Dimension(method.isStatic());
				newCase.putDimension(Constants.Dimension.Method.STATIC, methodIsStatic);

				Dimension methodIsSynchronized = new Dimension(method.isSynchronized());
				newCase.putDimension(Constants.Dimension.Method.SYNCRONIZED, methodIsSynchronized);

				caseList.add(newCase);
			}
		}
		return caseList;
	}

	public static ProjectDTO loadProjectInfo(List<DataClazz> dataClazz) {
		if (dataClazz == null || dataClazz.size() == 0 || dataClazz.get(0) == null)
			return null;

		DataClazz data = dataClazz.get(0);

		ProjectDTO project = new ProjectDTO();
		project.setProject_name(data.getClazzProject().getProjectName());
		project.setProject_data(data.getClazzProject().getProjectData());
		project.setProject_date(data.getClazzProject().getProjectDate());
		project.setProject_state(data.getClazzProject().getProjectState());
		project.setProject_expert(data.getClazzProject().getProjectExpert());
		project.setProject_module(data.getClazzProject().getProjectModule());
		project.setProject_file(data.getClazzProject().getProjectFile());

		return project;
	}

	public static RepositoryDTO loadRepositoryInfo(List<DataClazz> dataClazz) {
		DataClazz data = dataClazz.get(0);

		RepositoryDTO repository = new RepositoryDTO();

		repository.setRepository_name(data.getClazzRepository().getRepositoryUrl());
		repository.setRepository_protocol(data.getClazzRepository().getRepositoryType());
		repository.setRepository_server(data.getClazzRepository().getRepositoryServer());
		repository.setRepository_port(data.getClazzRepository().getRepositoryPort());
		repository.setRepository_user(data.getClazzRepository().getRepositoryUsr());
		repository.setRepository_password(data.getClazzRepository().getRepositoryPwd());

		return repository;
	}
}
